/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package CCPVSimulationPackage;

import GISPackage.Region;
import GUIUtilities.CoalitionUtilities;
import GUIUtilities.ListUtilities;
import SocialMatrixPackage.SocialMatrix;
import ccpv.FileManager;
import ccpv.Person;
import ccpv.RunTimeSettings;
import ccpv.Utilities;
import coalition.formation.Population;
import coalition.formation.PopulationInterface;
import coherence.CoherenceBuilder;
import coherence.CoherenceInputsFromConflict;
import coherence.CoherenceInputsFromGlobals;
import coherence.CoherenceModule;
import coherence.CoherenceUpdater;
import function.ArslanConflictFunction;
import function.ConflictFunction;
import java.lang.Double;
import java.lang.Float;
import java.lang.String;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import java.util.Vector;
import javax.swing.JTree;
import multiarray.MultiArray;
import multiarray.RecursiveAccess;
import solver.Results;

/**
 * Represents one step from coalition formation to conflict to coherence.
 */
public class CCPVSimulationIteration
{

  // used to indicate what type of partition we have found
  public static enum PartitionTypes
  {

    CORE, MERGE_AND_SPLIT, NO_TREAT, NONE
  };
  private Region r;
  private RunTimeSettings rt;
  private CCPVMediator md;
  private CCPVSimulationPopulation simPop = new CCPVSimulationPopulation();
  private PopulationInterface coalitionFormer = null;
  private PartitionTypes partitionType = PartitionTypes.NONE;
  // cross sectional form partitions
  private List<List<List<int[]>>> partitionsAsCrossSections = new Vector<List<List<int[]>>>();
  HashMap<String, Double> constants;

  // list form partitions
  // for now we will ignore this one!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  //private List<List<int[]>> partitionsAsList = new Vector<List<int[]>>();
  private List<List<int[]>> chosenPartition;
  private Random randGen = new Random();
  private Results results;

  /*
   * Constructors
   */
  public CCPVSimulationIteration()
  {
    setupTestPartitions();
  }

  public CCPVSimulationIteration(SocialMatrix sm)
  {
    simPop.sm = sm;
    setRandomPartitions();
  }

  public CCPVSimulationIteration(RunTimeSettings rt)
  {
    this.rt = rt;
    simPop.sm = new SocialMatrix(CoalitionUtilities.getAscriptiveTree(rt.ascriptive_names));
  }

  public CCPVSimulationIteration(String robertsFile, Region r)
  {
    this.r = r;
    setupRTConstantsAndSimPop(robertsFile);
    instantiateCoalitionFormer();
    calculatePartitions();
  }

  public CCPVSimulationIteration(CCPVSimulationIteration old)
  {
    this.r = old.r;
    this.rt = old.rt;
    this.simPop = old.simPop;
    coalitionFormer = null;
    this.constants = old.constants;
    setupMediator();
    calculatePartitions();
  }

  private void instantiateCoalitionFormer()
  {
    coalitionFormer = Population.getPopulationInstance(simPop.sm.getDimensions(), md);
  }

  private void loadDummyConflictInputs(CoherenceInputsFromConflict conflictInputs, List<List<int[]>> chosenPartition)
  {
    conflictInputs.altruisms = simPop.sm.convertToFloats();				//altruism that each cross-section
    //has for every other cross-section
    conflictInputs.powerByCrossSection = simPop.getPowerFromPersonsAsFloats();	//power for each cross-section
    conflictInputs.populationByCrossSection = simPop.getPopulationFromPersonsAsFloats();	//power for each cross-section
    conflictInputs.partition = chosenPartition;						//the partition along which conflict occurred
  }

  private List<List<int[]>> getTestPartition()
  {
    List<List<int[]>> testPartition = new Vector<List<int[]>>();
    Vector<int[]> allCombos = simPop.sm.getAllCombinations();
    List<int[]> c1 = new Vector<int[]>();
    List<int[]> c2 = new Vector<int[]>();
    c1.add(allCombos.get(0));
    c2.add(allCombos.get(1));

    for (int i = 2; i < allCombos.size(); i++)
    {
      if (randGen.nextDouble() > 0.5)
      {
        c1.add(allCombos.get(i));
      }
      else
      {
        c2.add(allCombos.get(i));
      }
    }

    testPartition.add(c1);
    testPartition.add(c2);
    return testPartition;
  }

  public void setupRTConstantsAndSimPop(String filename)
  {
    setupRT(filename);
    setupPopulation();
    setupConstants();
    setupSimPopAltruisms();
    setupMediator();
    setupSimPopPopulation();
  }

  public void setupRT(String filename)
  {
    rt = FileManager.csvReader(filename);
    rt.setC(r.c);
    rt.setI(r.I);
    rt.setLambda(r.lambda);
    rt.setPhi(r.F); // I don't think we use this anymore
  }

  public void setupPopulation()
  {
    simPop.population = Utilities.makePeople(rt); // gets initial year only
  }

  public void setupSimPopPopulation()
  {    
    simPop.preConflictPowers = simPop.getPowerFromPersonsAsFloats();
  }

  public void setupConstants()
  {
    constants = new HashMap<String, Double>();
    constants.put("lambda", r.lambda);
    constants.put("c", r.c);
    constants.put("f", r.F);
    constants.put("minPower", 0.0);// hmmm...
  }

  public void setupSimPopAltruisms()
  {
    simPop.sm = new SocialMatrix(CoalitionUtilities.getAscriptiveTree(rt.ascriptive_names));
    simPop.setSMAltruisms(simPop.population);
  }

  public void setupMediator()
  {
    ConflictFunction func = new ArslanConflictFunction(simPop.population, constants);
    md = new CCPVMediator(func, simPop.sm.convertToFloats(), simPop.getPopulationFromPersonsAsFloats(), simPop.getPowerFromPersonsAsFloats());
    md.dimensions = simPop.sm.getDimensions();
  }

  private void setupTestPartitions()
  {
    simPop.sm = new SocialMatrix(new JTree());
    Vector<int[]> allCombos = simPop.sm.getAllCombinations();

    // first generate a random sample population
    int numberOfGroups = 15;
    Vector<Integer> chosen = new Vector<Integer>();
    while (chosen.size() < numberOfGroups)
    {
      int newIndex = randGen.nextInt(allCombos.size());
      if (!chosen.contains(newIndex))
      {
        chosen.add(newIndex);
      }
    }

    int numberOfPartitions = 5;
    for (int r = 0; r < numberOfPartitions; r++)
    {
      int numberOfCoalitions = randGen.nextInt(5) + 1;
      int chosenIndex = 0;

      List<List<int[]>> newCoalitions = new Vector<List<int[]>>();
      for (int i = 0; i < numberOfCoalitions; i++)
      {
        List<int[]> coalition = new Vector<int[]>();
        coalition.add(allCombos.get(chosen.get(chosenIndex)));
        newCoalitions.add(coalition);
        chosenIndex++;
      }

      // add remaining groups randomly
      for (int i = chosenIndex; i < chosen.size(); i++)
      {
        int coalitionIndex = randGen.nextInt(numberOfCoalitions);
        newCoalitions.get(coalitionIndex).add(allCombos.get(chosen.get(chosenIndex)));
        chosenIndex++;
      }

      partitionsAsCrossSections.add(newCoalitions);
    }
  }

  // testing crap
  public List<Integer> participants = null;

  public void setRandomPartitions()
  {
    partitionsAsCrossSections.clear();
    if (participants == null)
    {
      participants = new Vector<Integer>();
      Vector<int[]> allCombos = simPop.sm.getAllCombinations();
      int numberOfParticipants = 15;

      int maxNum = allCombos.size();
      if (numberOfParticipants > maxNum)
      {
        numberOfParticipants = maxNum;
      }
      while (participants.size() < numberOfParticipants)
      {
        System.out.println("participants size: " + participants.size());
        int participant = randGen.nextInt(allCombos.size());

        if (!participants.contains(participant))
        {
          participants.add(participant);
        }
      }
    }
    for (int i = 0; i < 5; i++)
    {
      partitionsAsCrossSections.add(getARandomPartition(participants));
    }
  }

  public List<List<int[]>> getARandomPartition(List<Integer> participants)
  {
    List<List<int[]>> ret = new Vector<List<int[]>>();

    int numberOfCoalitions = randGen.nextInt(4) + 1;
    Vector<int[]> allCombos = simPop.sm.getAllCombinations();
    for (int i = 0; i < numberOfCoalitions; i++)
    {
      Vector<int[]> coalition = new Vector<int[]>();
      coalition.add(allCombos.get(participants.get(i)));
      ret.add(coalition);
    }

    for (int i = numberOfCoalitions; i < participants.size(); i++)
    {
      int coalitionIndex = randGen.nextInt(numberOfCoalitions);
      ret.get(coalitionIndex).add(allCombos.get(participants.get(i)));
    }

    return ret;
  }

  private List<List<int[]>> removeDuplicates(List<List<int[]>> inputCoalitions)
  {
    List<List<int[]>> newCoal = new Vector<List<int[]>>();
    Vector<int[]> seenArrays = new Vector<int[]>();

    for (int i = 0; i < inputCoalitions.size(); i++)
    {
      List<int[]> coalition = inputCoalitions.get(i);
      List<int[]> newCoalition = new Vector<int[]>();

      for (int[] array : coalition)
      {
        boolean seen = false;

        for (int[] compare : seenArrays)
        {
          boolean equal = true;
          for (int j = 0; j < array.length; j++)
          {
            if (array[j] != compare[j])
            {
              equal = false;
            }
          }
          if (equal)
          {
            seen = true;
            break;
          }
        }

        if (!seen)
        {
          newCoalition.add(array);
          seenArrays.add(array);
        }
      }

      newCoal.add(newCoalition);
    }

    return newCoal;
  }

  // returns true if partitions were found
  public boolean calculatePartitions()
  {
    int[] dimensions = simPop.sm.getDimensions();
    if (partitionsAsCrossSections.isEmpty()) // first try core
    {
      System.out.println("TRYING CORE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
      // for each ascriptive, find the proper partition
      for (int i = 0; i < dimensions.length; i++)
      {
        instantiateCoalitionFormer();
        List<List<List<int[]>>> iSections = coalitionFormer.getCoreByCrossSection(i);
        for (List<List<int[]>> l : iSections)
        {
          partitionsAsCrossSections.add(l);
        }
      }

      if (!partitionsAsCrossSections.isEmpty())
      {
        System.out.println("core found!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
        partitionType = PartitionTypes.CORE;
        return true;
      }
      else // next try merge and split proof
      {
        System.out.println("TRYING SPLIT/MERGE PROOF!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
        // for each ascriptive, find the proper partition
        for (int i = 0; i < dimensions.length; i++)
        {
          instantiateCoalitionFormer();
          List<List<List<int[]>>> iSections = coalitionFormer.getMergeProofSplitProofPartitionsByCrossSection(i);
          for (List<List<int[]>> l : iSections)
          {
            partitionsAsCrossSections.add(l);
          }
        }

        if (!partitionsAsCrossSections.isEmpty())
        {
          partitionType = PartitionTypes.MERGE_AND_SPLIT;
          return true;
        }
        else // finally try no treat
        {
          System.out.println("TRYING NO TREAT!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
          // for each ascriptive, find the proper partition
          for (int i = 0; i < dimensions.length; i++)
          {
            instantiateCoalitionFormer();
            List<List<List<int[]>>> iSections = coalitionFormer.getNoTreatPartitionsByCrossSection(i);
            for (List<List<int[]>> l : iSections)
            {
              partitionsAsCrossSections.add(l);
            }
          }

          if (!partitionsAsCrossSections.isEmpty())
          {
            partitionType = PartitionTypes.NO_TREAT;
            return true;
          }
          else // nothing works, return false
          {
            return false;
          }
        }
      }
    }

    // this means partitions are already filled out as core, so return true;
    if (!partitionsAsCrossSections.isEmpty())
    {
      return true;
    }
    else
    {
      return false;
    }
  }

  public List<List<List<int[]>>> getAllPartitions()
  {

    return partitionsAsCrossSections;
  }

  public Results runConflict(List<List<int[]>> chosenPartition)
  {
    this.chosenPartition = chosenPartition;
    float[][] betas = md.getCoalitionBetas(chosenPartition);
    md.func.setCoalitions(chosenPartition, betas);
    results = md.func.solve_and_update_agents();
    return results;
  }

  public void runCoherence()
  {
    //first create a builder object
    CoherenceInputsFromGlobals globalInputs = new CoherenceInputsFromGlobals();
    loadGlobalInputs(globalInputs);
    CoherenceInputsFromConflict conflictInputs = new CoherenceInputsFromConflict();
    //Results res = getDummyResults();
    if (results != null)
    {
      if (results.isConflict())
      {
        loadConflictInputs(conflictInputs, chosenPartition, results);
        CoherenceBuilder coherenceBuilder = CoherenceBuilder.getCoherenceBuilder(conflictInputs, globalInputs);
        // have the builder make the updater
        CoherenceUpdater coherenceUpdater = new CoherenceModule(coherenceBuilder);
        // get altruisms from robert... or from my social matrix
        MultiArray<MultiArray<Float>> altruisms = simPop.sm.convertToFloats();
        // get new altruisms
        MultiArray<MultiArray<Float>> newAltruisms = coherenceUpdater.getNewAltruisms(altruisms);

        /******
         *
         * Testing
         ******/
        System.out.println("*********************************************************************");

        //System.out.println(getAltruismPrintString(getAltruismDifferences(newAltruisms, altruisms)));
        //System.out.println(getAltruismPrintString(altruisms));
        //System.out.println(getAltruismPrintString(newAltruisms));
        /*
        List<int[]> allCombos = RecursiveAccess.generateAllPermutations(simPop.population.getDimensions());
        for (int[] index : allCombos)
        {
        Person p = simPop.population.getElementAtIndex(index);
        for (int[] target : allCombos)
        {
        double altruism = p.getAltruism(target);
        System.out.println(altruism);
        }
        }
         */
        System.out.println("*********************************************************************");

        // update altruisms in roberts person object and my social matrix
        simPop.updateAltruisms(ListUtilities.convertFloatsToDoubles(newAltruisms));
      }
    }
  }

  private void loadGlobalInputs(CoherenceInputsFromGlobals globalInputs)
  {
    globalInputs.lambda = (float) r.lambda;
    globalInputs.c = (float) r.c;
    globalInputs.I = (float) r.I;
    globalInputs.epsilon = 0.5f; // default
  }

  private void loadConflictInputs(CoherenceInputsFromConflict conflictInputs, List<List<int[]>> chosenPartition, Results res)
  {
    conflictInputs.altruisms = simPop.sm.convertToFloats();				//altruism that each cross-section
    //has for every other cross-section
    // before roberts module!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    //conflictInputs.powerByCrossSection = simPop.getPowerFromPersonsAsFloats();	//power for each cross-section
    conflictInputs.powerByCrossSection = simPop.getPreConflictPowersAsFloats();
    conflictInputs.populationByCrossSection = simPop.getPopulationFromPersonsAsFloats();	//power for each cross-section
    conflictInputs.partition = chosenPartition;						//the partition along which conflict occurred

    //these are structures that do not contain the m-dimensional structure of a population
    //they do not treat coalition elements as cross-sections but as abstract agents

    conflictInputs.theta_i_j = res.getThetasF();		//resource allocation at the coalition level
    conflictInputs.won_i_j = res.getWinners();			//did i beat j?
  }

  // returns altruism1 - altruism2
  public MultiArray<MultiArray<Float>> getAltruismDifferences(MultiArray<MultiArray<Float>> altruism1, MultiArray<MultiArray<Float>> altruism2)
  {
    MultiArray<MultiArray<Float>> ret = new MultiArray<MultiArray<Float>>(altruism1.getDimensions());

    List<int[]> allCombos = RecursiveAccess.generateAllPermutations(altruism1.getDimensions());
    for (int[] i : allCombos)
    {
      MultiArray<Float> feelings = new MultiArray<Float>(altruism1.getDimensions());
      MultiArray<Float> alt1Feelings = altruism1.getElementAtIndex(i);
      MultiArray<Float> alt2Feelings = altruism2.getElementAtIndex(i);

      for (int[] j : allCombos)
      {
        float a1 = alt1Feelings.getElementAtIndex(j);
        float a2 = alt2Feelings.getElementAtIndex(j);

        float difference = a1 - a2;
        feelings.setElementAtIndex(j, difference);
      }

      ret.setElementAtIndex(i, feelings);
    }

    return ret;
  }

  public String getAltruismPrintString(MultiArray<MultiArray<Float>> altruism)
  {
    String ret = "";

    List<int[]> allCombos = RecursiveAccess.generateAllPermutations(altruism.getDimensions());
    for (int[] source : allCombos)
    {
      for (int[] target : allCombos)
      {
        String sourceString = ListUtilities.getIntArrayString(source);
        String targetString = ListUtilities.getIntArrayString(target);

        ret += sourceString + ", " + targetString + ": " + altruism.getElementAtIndex(source).getElementAtIndex(target);
        ret += "\n";
      }
    }

    return ret;
  }

  public double getConflictExpediture(List<int[]> coalition)
  {
    double totalPower = 0;
    for (int[] group : coalition)
    {
      double groupPower = simPop.population.getElementAtIndex(group).getPower() * simPop.population.getElementAtIndex(group).getQty();
      totalPower += groupPower;
    }

    //System.out.println("region c: " + r.c);
    return totalPower * r.c;
  }

  public List<List<List<int[]>>> getPartitionsAsCrossSection()
  {
    return partitionsAsCrossSections;
  }

  public SocialMatrix getSM()
  {
    return simPop.sm;
  }

  public CCPVSimulationPopulation getSimPop()
  {
    return simPop;
  }

  public PartitionTypes getPartitionType()
  {
    return partitionType;
  }
}
